// 导入所需的Node.js内置模块和外部库
import { exec as _exec, spawn } from 'node:child_process' // 导入执行命令和生成子进程的模块
import fs from 'node:fs' // 导入文件系统模块
import path from 'node:path' // 导入路径处理模块
import util from 'node:util' // 导入工具模块
import { adbPath } from '$electron/configs/index.js' // 导入ADB路径配置
import appStore from '$electron/helpers/store.js' // 导入应用存储模块
import { formatFileSize } from '$renderer/utils/index' // 导入文件大小格式化工具
import { Adb } from '@devicefarmer/adbkit' // 导入ADB工具库
import dayjs from 'dayjs' // 导入日期处理库
import { uniq } from 'lodash-es' // 导入去重工具

// 将exec函数转换为Promise版本，以便异步使用
const exec = util.promisify(_exec)

// 定义一个变量来存储Adb客户端实例，用于与设备进行交互
let client = null

// 在窗口卸载前确保Adb客户端被正确关闭，防止资源泄漏
window.addEventListener('beforeunload', () => {
  if (client) {
    client.kill() // 关闭Adb客户端
  }
})

// 监听adbPath的变更，确保使用最新的路径，并重新初始化Adb客户端
appStore.onDidChange('common.adbPath', async (value, oldValue) => {
  if (value === oldValue) {
    return false // 如果新旧值相同，直接返回
  }

  if (value === client?.options?.bin) {
    return false // 如果新值与当前客户端使用的路径相同，直接返回
  }

  if (client) {
    await client.kill().catch(e => console.warn(e)) // 关闭当前客户端
    client = null // 重置客户端
  }

  client = Adb.createClient({ bin: value || adbPath }) // 创建新的Adb客户端
})

/**
 * 执行shell命令
 * @param {string} command - 要执行的命令字符串
 * @returns {Promise} - 返回一个Promise，解析为命令的输出结果
 */
const shell = async (command) => {
  const execPath = appStore.get('common.adbPath') || adbPath // 获取ADB路径
  return exec(`"${execPath}" ${command}`, { // 执行命令
    env: { ...process.env }, // 继承环境变量
    shell: true, // 使用shell执行
  })
}

/**
 * 生成一个spawn进程来执行命令，并处理stdout和stderr
 * @param {string} command - 要执行的命令字符串
 * @param {Object} options - 可选的回调函数处理stdout和stderr
 * @param {Function} [options.stdout] - 处理stdout数据的回调函数
 * @param {Function} [options.stderr] - 处理stderr数据的回调函数
 * @returns {Promise} - 返回一个Promise，当进程结束时解析或拒绝
 */
const spawnShell = async (command, { stdout, stderr } = {}) => {
  const spawnPath = appStore.get('common.adbPath') || adbPath // 获取ADB路径
  const args = command.split(' ') // 将命令拆分为参数数组

  const spawnProcess = spawn(`"${spawnPath}"`, args, { // 生成子进程
    env: { ...process.env }, // 继承环境变量
    shell: true, // 使用shell执行
    encoding: 'utf8', // 设置编码
  })

  // 监听stdout数据并调用回调函数
  spawnProcess.stdout.on('data', (data) => {
    const stringData = data.toString() // 将数据转换为字符串

    if (stdout) {
      stdout(stringData, spawnProcess) // 调用stdout回调函数
    }
  })

  const stderrList = [] // 存储stderr数据
  // 监听stderr数据并调用回调函数
  spawnProcess.stderr.on('data', (data) => {
    const stringData = data.toString() // 将数据转换为字符串

    stderrList.push(stringData) // 存储stderr数据

    console.error('spawnProcess.stderr.data:', stringData) // 打印stderr数据

    if (stderr) {
      stderr(stringData, spawnProcess) // 调用stderr回调函数
    }
  })

  // 当进程关闭时解析或拒绝Promise
  return new Promise((resolve, reject) => {
    spawnProcess.on('close', (code) => {
      if (code === 0) {
        resolve() // 进程成功结束，解析Promise
      } else {
        reject( // 进程失败，拒绝Promise
          new Error(stderrList.join(',') || `Command failed with code ${code}`),
        )
      }
    })

    // 当进程发生错误时拒绝Promise
    spawnProcess.on('error', (err) => {
      reject(err)
    })
  })
}

/**
 * 获取所有连接的设备列表
 * @returns {Promise<Array>} - 返回一个Promise，解析为设备列表
 */
const getDevices = async () => client.listDevicesWithPaths()

/**
 * 在指定设备上执行命令
 * @param {string} id - 设备ID
 * @param {string} command - 要执行的命令字符串
 * @returns {Promise<string>} - 返回一个Promise，解析为命令的输出结果
 */
const deviceShell = async (id, command) => {
  const res = await client.getDevice(id).shell(command).then(Adb.util.readAll) // 在指定设备上执行命令
  return res.toString() // 将结果转换为字符串
}

/**
 * 关闭Adb客户端
 * @param {...any} params - 传递给kill函数的参数
 * @returns {Promise} - 返回一个Promise，当客户端成功关闭时解析
 */
const kill = async (...params) => client.kill(...params)

/**
 * 连接到一个设备
 * @param {...any} params - 传递给connect函数的参数
 * @returns {Promise} - 返回一个Promise，当连接成功时解析
 */
const connect = async (...params) => client.connect(...params)

/**
 * 断开与设备的连接
 * @param {...any} params - 传递给disconnect函数的参数
 * @returns {Promise} - 返回一个Promise，当断开连接成功时解析
 */
const disconnect = async (...params) => client.disconnect(...params)

/**
 * 获取设备的IP地址
 * @param {string} id - 设备ID
 * @returns {Promise<string>} - 返回一个Promise，解析为设备的IP地址
 */
const getDeviceIP = async (id) => {
  try {
    const { stdout } = await shell(`-s ${id} shell ip -f inet addr show wlan0`) // 执行命令获取设备IP
    const reg = /inet ([0-9.]+)\/\d+/ // 匹配IP地址的正则表达式
    const match = stdout.match(reg) // 匹配IP地址
    const value = match[1] // 提取匹配到的IP地址

    return value // 返回IP地址
  } catch (error) {
    console.warn('adbkit.getDeviceIP.error', error.message) // 捕获并打印错误
  }
}

/**
 * 启动设备的TCP/IP模式
 * @param {string} id - 设备ID
 * @param {number} [port=5555] - 监听的端口号，默认为5555
 * @returns {Promise} - 返回一个Promise，当设备成功启动TCP/IP模式时解析
 */
const tcpip = async (id, port = 5555) => client.getDevice(id).tcpip(port)

/**
 * 截取设备屏幕的截图
 * @param {string} deviceId - 设备ID
 * @param {Object} [options={}] - 可选的配置对象
 * @param {string} [options.savePath] - 保存截图的路径，默认为当前目录
 * @returns {Promise<boolean>} - 返回一个Promise，当截图成功保存时解析为true，否则为false
 */
const screencap = async (deviceId, options = {}) => {
  let fileStream = null
  try {
    const device = client.getDevice(deviceId) // 获取设备
    fileStream = await device.screencap() // 获取设备屏幕截图
  } catch (error) {
    console.warn(error?.message || error) // 捕获并打印错误
    return false // 返回false表示失败
  }

  if (!fileStream) {
    return false // 如果没有获取到截图流，返回false
  }

  const fileName = `Screencap-${dayjs().format('YYYY-MM-DD-HH-mm-ss')}.png` // 生成文件名
  const savePath = options.savePath || path.resolve('../', fileName) // 生成保存路径

  return new Promise((resolve, reject) => {
    fileStream
      .pipe(fs.createWriteStream(savePath)) // 将截图流写入文件
      .on('finish', () => {
        resolve(true) // 文件写入成功，解析Promise
      })
      .on('error', (error) => {
        console.warn(error?.message || error) // 捕获并打印错误
        reject(false) // 文件写入失败，拒绝Promise
      })
  })
}

// 安装应用到指定设备
/**
 * @param {string} id - 设备ID
 * @param {string} path - 应用路径
 * @returns {Promise<void>} 安装结果
 */
const install = async (id, path) => client.getDevice(id).install(path)

// 检查指定设备上是否已安装某个应用
/**
 * @param {string} id - 设备ID
 * @param {string} pkg - 应用包名
 * @returns {Promise<boolean>} 是否已安装
 */
const isInstalled = async (id, pkg) => client.getDevice(id).isInstalled(pkg)

// 获取客户端版本信息
/**
 * @returns {Promise<string>} 客户端版本
 */
const version = async () => client.version()

// 获取指定设备的显示信息
/**
 * @param {string} deviceId - 设备ID
 * @returns {Promise<Array<string>>} 显示信息列表
 */
const display = async (deviceId) => {
  let value = [] // 初始化返回值
  try {
    const res = await deviceShell(deviceId, 'dumpsys display') // 执行命令获取显示信息

    const regex = /Display Id=(\d+)/g // 匹配显示ID的正则表达式

    const match = res.match(regex) || [] // 匹配所有显示ID

    const mapValue = match.map(item => item.split('=')[1]) // 提取显示ID

    value = uniq(mapValue) // 去重
  }
  catch (error) {
    console.warn(error?.message || error) // 捕获并记录错误
  }

  return value // 返回显示信息列表
}

// 清除指定设备上的覆盖显示设备设置
/**
 * @param {string} deviceId - 设备ID
 * @returns {Promise<void>} 清除结果
 */
const clearOverlayDisplayDevices = async (deviceId) => {
  return deviceShell(
    deviceId,
    'settings put global overlay_display_devices none', // 设置覆盖显示设备为空
  )
}

// 监视设备变化并执行回调函数
/**
 * @param {function} callback - 回调函数
 * @returns {function} 关闭监视的函数
 */
const watch = async (callback) => {
  const tracker = await client.trackDevices() // 开始跟踪设备变化
  tracker.on('add', async (ret) => {
    const host = await getDeviceIP(ret.id) // 获取新设备的IP地址
    callback('add', { ...ret, $host: host }) // 调用回调函数
  })

  tracker.on('remove', (device) => {
    callback('remove', device) // 调用回调函数
  })

  tracker.on('end', (ret) => {
    callback('end', ret) // 调用回调函数
  })

  tracker.on('error', (err) => {
    callback('error', err) // 调用回调函数
  })

  const close = () => tracker.end() // 关闭跟踪器

  return close // 返回关闭函数
}

// 读取指定设备的指定目录下的文件和子目录信息
/**
 * @param {string} id - 设备ID
 * @param {string} filePath - 目录路径
 * @returns {Promise<Array<object>>} 文件和子目录信息列表
 */
async function readdir(id, filePath) {
  const value = await client.getDevice(id).readdir(filePath) // 获取目录信息

  return value.map(item => ({
    ...item,
    id: [filePath, item.name].join('/'), // 构建文件或目录的唯一ID
    type: item.isFile() ? 'file' : 'directory', // 判断文件类型
    name: item.name, // 文件或目录名称
    size: formatFileSize(item.size), // 格式化文件大小
    updateTime: dayjs(item.mtime).format('YYYY-MM-DD HH:mm:ss'), // 格式化修改时间
  }))
}

// 将文件推送到指定设备
/**
 * @param {string} id - 设备ID
 * @param {string} filePath - 本地文件路径
 * @param {object} args - 附加参数
 * @param {function} [args.progress] - 进度回调函数
 * @param {string} [args.savePath='/sdcard/Download'] - 保存路径
 * @returns {Promise<string>} 保存路径
 */
async function push(id, filePath, args = {}) {
  const { progress, savePath = '/sdcard/Download' } = args // 解构参数

  const fileName = path.basename(filePath) // 获取文件名

  const fullSavePath = `${savePath}/${fileName}`.replace(/\/+/g, '/') // 构建完整的保存路径

  const transfer = await client.getDevice(id).push(filePath, fullSavePath) // 开始文件传输

  return new Promise((resolve, reject) => {
    transfer.on('progress', (stats) => {
      progress?.(stats) // 调用进度回调函数
    })

    transfer.on('end', () => {
      resolve(fullSavePath) // 传输完成，返回保存路径
    })

    transfer.on('error', (err) => {
      reject(err) // 传输失败，返回错误
    })
  })
}

// 从指定设备拉取文件
/**
 * @param {string} id - 设备ID
 * @param {string} filePath - 设备上的文件路径
 * @param {object} args - 附加参数
 * @param {function} [args.progress] - 进度回调函数
 * @param {string} [args.savePath='../'] - 本地保存路径
 * @returns {Promise<string>} 本地保存路径
 */
async function pull(id, filePath, args = {}) {
  const { progress, savePath = '../' } = args // 解构参数

  const fileName = path.basename(filePath) // 获取文件名

  const fullSavePath = path.resolve(savePath, fileName) // 构建完整的保存路径

  const transfer = await client.getDevice(id).pull(filePath) // 开始文件传输

  return new Promise((resolve, reject) => {
    transfer.on('progress', (stats) => {
      progress?.(stats) // 调用进度回调函数
    })

    transfer.on('end', () => {
      resolve(fullSavePath) // 传输完成，返回保存路径
    })

    transfer.on('error', (err) => {
      reject(err) // 传输失败，返回错误
    })

    transfer.pipe(fs.createWriteStream(fullSavePath)) // 将数据流写入本地文件
  })
}

// 导出默认函数，初始化ADB客户端并提供各种操作函数
/**
 * @returns {object} ADB操作函数集合
 */
export default () => {
  const binPath = appStore.get('common.adbPath') || adbPath // 获取ADB路径

  client = Adb.createClient({
    bin: binPath, // 初始化ADB客户端
  })

  return {
    shell,
    spawnShell,
    getDevices,
    deviceShell,
    kill,
    connect,
    disconnect,
    getDeviceIP,
    tcpip,
    screencap,
    install,
    isInstalled,
    version,
    display,
    clearOverlayDisplayDevices,
    push,
    pull,
    watch,
    readdir,
  }
}
